package simulator.ui;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.Vector;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

import simulator.Scenario;
import simulator.SchedulingScenario;
import simulator.software.Process;
import simulator.software.ProcessState;

/**
 * The <code>SimulationController</code> serves to orchestrate the running of
 * scenarios and to expose their data to the view. In this way, the
 * <code>SimulationController</code> acts as the controller in the classical
 * Model-View-Controller pattern, along with a <code>ScenarioRenderer</code> as
 * the view and a <code>Scenario</code> as the model.
 * 
 * @author Kyle Loomis
 * 
 */
public class SimulationController
{
   private Timer timer = null;
   private int delay = 500;
   private Collection<SimulationListener> listeners;
   private Scenario scenario = null;
   private ScenarioRunner runner = null;
   private ScenarioRenderer renderer = null;
   private int status = 0;
   private JFrame frame = null;
   private JComponent display = null;

   public static int STATUS_RUNNING = 1;
   public static int STATUS_STOPPED = 2;

   /**
    * Creates a <code>SimulationController</code>.
    */
   public SimulationController()
   {
      timer = new Timer();
      listeners = new Vector<SimulationListener>();
   }

   /**
    * Registers the provided listener for callbacks based on simulation events.
    * 
    * @param listener
    *           the listener to add.
    */
   public void addSimulationListener(SimulationListener listener)
   {
      listeners.add(listener);
   }

   /**
    * Sets the delay in between GUI updates, in milliseconds.
    * 
    * @param d
    *           the delay between updates of the GUI, in milliseconds.
    */
   public void setDelay(int d)
   {
      delay = d;
   }

   /**
    * Gets the delay between GUI updates, in milliseconds.
    * 
    * @return the delay between GUI updates, in milliseconds.
    */
   public int getDelay()
   {
      return delay;
   }

   /**
    * Gets the current status of the scenario within this controller.
    * 
    * @return the status of the current scenario. The value returned is either
    *         STATUS_RUNNING or STATUS_STOPPED.
    */
   public int getStatus()
   {
      return status;
   }

   /**
    * Sets the scenario associated with this controller.
    * 
    * @param scenario
    *           the scenario to associate with this controller.
    */
   public void setScenario(Scenario scenario)
   {
      stop();
      this.scenario = scenario;

      if (scenario != null)
      {
         // Schedule a job for the event-dispatching thread:
         // creating and showing this application's GUI.
         SwingUtilities.invokeLater(new Runnable()
         {
            public void run()
            {
               resolveUI();
            }
         });

      }
      else
      {
         renderer = null;
      }

      fireOnScenarioChanged();
   }

   private void resolveUI()
   {
      if (scenario instanceof SchedulingScenario)
      {
         renderer = new SchedulingScenarioRenderer(
               (SchedulingScenario) scenario);
         buildUI();
      }
   }

   private void buildUI()
   {
      frame.remove(display);

      GridBagConstraints constraints = new GridBagConstraints();
      constraints.fill = GridBagConstraints.BOTH;
      constraints.weightx = 1;
      constraints.weighty = 1;
      constraints.gridwidth = 3;
      constraints.gridheight = 1;
      constraints.gridx = 0;
      constraints.gridy = 2;
      constraints.anchor = GridBagConstraints.LINE_START;
      renderer.setBackground(Color.WHITE);
      JScrollPane pane = new JScrollPane(renderer);
      pane.setBorder(null);
      frame.getContentPane().add(pane, constraints);
      frame.invalidate();
      frame.setVisible(true);
      frame.repaint();
      setDisplay(pane);
      addSimulationListener(renderer);
   }

   /**
    * Shows a scenario summary dialog.
    */
   public void showSummary()
   {
      // Schedule a job for the event-dispatching thread:
      // creating and showing this application's GUI.
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            buildSummaryScreen();
         }
      });
   }

   private void buildSummaryScreen()
   {
      StatisticsDialog dialog = new StatisticsDialog(this, frame);
   }

   /**
    * Gets the scenario associated with this controller.
    * 
    * @return the scenario associated with this controller.
    */
   public Scenario getScenario()
   {
      return scenario;
   }

   public void setDisplay(JComponent display)
   {
      this.display = display;
   }

   private void fireOnScenarioStart()
   {
      for (Iterator<SimulationListener> i = listeners.iterator(); i.hasNext();)
      {
         SimulationListener listener = i.next();
         listener.onScenarioStart();
      }
   }

   private void fireOnScenarioStop()
   {
      for (Iterator<SimulationListener> i = listeners.iterator(); i.hasNext();)
      {
         SimulationListener listener = i.next();
         listener.onScenarioStop();
      }
   }

   private void fireOnScenarioChanged()
   {
      for (Iterator<SimulationListener> i = listeners.iterator(); i.hasNext();)
      {
         SimulationListener listener = i.next();
         listener.onScenarioChanged();
      }
   }

   /**
    * Starts running the scenario.
    */
   public void start()
   {
      status = STATUS_RUNNING;
      runner = new ScenarioRunner(this);
      fireOnScenarioStart();
      timer.schedule(runner, 0, delay);
   }

   /**
    * Pauses the scenario.
    */
   public void pause()
   {
      status = STATUS_STOPPED;
      fireOnScenarioStop();

      if (runner != null)
      {
         runner.cancel();
      }

      timer.purge();
   }

   /**
    * Resumes running the scenario after a pause.
    */
   public void resume()
   {

   }

   /**
    * Stops running the scenario.
    */
   public void stop()
   {
      status = STATUS_STOPPED;
      fireOnScenarioStop();

      if (runner != null)
      {
         runner.cancel();
      }

      timer.purge();

      if (scenario != null)
      {
         scenario.seek(0);
      }
   }

   /**
    * Moves the scenario forward by one unit of time (step).
    */
   public void step()
   {
      boolean activeFound = false;

      List<Process> processTable = scenario.getComputer().getKernel()
            .getProcesses();

      for (Process p : processTable)
      {
         if (p.getState() != ProcessState.DONE)
         {
            activeFound = true;
         }
      }

      if (!activeFound)
      {
         stop();
         showSummary();
      }
      else
      {
         scenario.step();
         fireOnScenarioChanged();
      }
   }

   /**
    * Gets the <code>ScenarioRenderer</code> associated with the current
    * scenario.
    * 
    * @return the <code>ScenarioRenderer</code> instance associated with the
    *         current scenario.
    */
   public ScenarioRenderer getRenderer()
   {
      return renderer;
   }

   /**
    * Sets the frame that this controller should paint to.
    * 
    * @param frame
    *           the frame that this controller should paint to.
    */
   public void setFrame(JFrame frame)
   {
      this.frame = frame;
   }
}
